using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using System.Linq;

public class PlayerStateMachine : MonoBehaviour
{
    public float speed;
    public float fallingMoveSpeed;
    [SerializeField] private bool isTouchingGround;
    public bool IsTouchingGround
    {
        get => isTouchingGround;
    }
    private PlayerStateBase currentState;
    public playerComponentContex playerComponentContex;
    //让当前状态可视化
    [SerializeField] private EPlayerState playerState;
    private Dictionary<EPlayerState, PlayerStateBase> playerStates;
    public bool canInput;
    private const EPlayerState InitState = EPlayerState.Idle;
    public Vector3 playerInputDirction;
    public static PlayerStateMachine Instance;
    
    private void Awake()
    {
        Instance = this;
        ConstructStates();
        SetState(InitState);
        canInput = true;
    }

    // Update is called once per frame
    void Update()
    {
        playerState = currentState.GetType().GetCustomAttribute<PlayerStateScriptAttribute>().playerState;
    }

    private void FixedUpdate()
    {
        currentState.FixedUpdate();
    }


    private void LateUpdate()
    {
        currentState.Update();
    }

    private void ConstructStates()
    {
        playerStates = new Dictionary<EPlayerState, PlayerStateBase>();

        var occuredTable = new Dictionary<EPlayerState, bool>();

        var allTypes = Assembly.GetExecutingAssembly().GetTypes();

        var attributedType =
            from type in allTypes
            where
                !type.IsAbstract &&
                !type.IsInterface &&
                type.IsSubclassOf(typeof(PlayerStateBase)) &&
                type.GetCustomAttributes<PlayerStateScriptAttribute>() != null
            select type;
        foreach (var type in attributedType)
        {
            var attribute = type.GetCustomAttribute<PlayerStateScriptAttribute>();
            var state = attribute.playerState;
            if (!occuredTable.ContainsKey(state))
            {
                occuredTable[state] = true;
                playerStates[state] =
                    type.GetConstructor(new Type[] {typeof(PlayerStateMachine)})?.Invoke(new object[] {this}) as
                        PlayerStateBase;
            }
            else
            {
                {
                    Debug.LogError("Repeated definition:" + type.Name);
                }
            }
        }
    }

    public void HandleInput(EPlayerInput playerInput, EInputType inputType)
    {
        currentState.HandleInput(playerInput,inputType);
    }

    public void SetState(EPlayerState state)
    {
        if (currentState != null)
        {
            currentState.ExitState();
        }

        var nextState = GetPlayerState(state);
        if (nextState == null) return;
        
        nextState.EnterState();
        currentState = nextState;
    }

    public PlayerStateBase GetPlayerState(EPlayerState state)
    {
        if (playerStates.ContainsKey(state))
        {
            return playerStates[state];
        }
        else
        {
            Debug.LogError("Required state not exist:" + state);
            return null;
        }
    }

    public void SetOnGround(bool isTouching)
    {
        isTouchingGround = isTouching;
    }
}


public class PlayerStateScriptAttribute : Attribute
{
    public EPlayerState playerState;

    public PlayerStateScriptAttribute(EPlayerState playerState)
    {
        this.playerState = playerState;
    }
}


[Serializable]
public enum EDirection
{
    left,right
}

public abstract class PlayerStateBase
{
    protected PlayerStateMachine stateMachine;
    public EDirection playerDirction;

    public PlayerStateBase(PlayerStateMachine sm)
    {
        stateMachine = sm;
    }

    public virtual void Update()
    {
        if (stateMachine.playerInputDirction.magnitude != 0)
        {
            // Debug.Log(stateMachine.playerInputDirction.x);
            double TOLERANCE = 0.1f;
            if(Math.Abs(stateMachine.playerInputDirction.x - 1) < TOLERANCE) playerDirction = EDirection.right;
            else if (Math.Abs(stateMachine.playerInputDirction.x - (-1)) < TOLERANCE) playerDirction = EDirection.left;
            
            Flip(playerDirction);
        }
        
    }
    
    public virtual void FixedUpdate() { }
    
    
    private void Flip(EDirection direction)
    {
        // Debug.Log("Flip");
        var transform = stateMachine.playerComponentContex.playerTransform;
        var forward = transform.forward;
        if (direction == EDirection.left)
        {
            forward = Vector3.left;
        }
        else
        {
            forward = Vector3.right;
        }
        transform.forward = forward;
    }

    public abstract void EnterState();
    public abstract void ExitState();
    public abstract void HandleInput(EPlayerInput playerInput,EInputType inputType);

}

//To-DO:用反射自动获取
[Serializable]
public class playerComponentContex
{
    public Rigidbody2D rigidbody;
    public Animator animator;
    public Transform playerTransform;
}

public enum EPlayerState
{
    Idle,Walk,Jump,Fall,Throw
}